"""Utility rules used to compile Verilator"""

def _verilator_astgen_impl(ctx):
    args = ctx.actions.args()
    args.add("--astgen", ctx.file.astgen)
    args.add_all(ctx.files.srcs, format_each = "--src=%s")
    args.add_all(ctx.outputs.outs, format_each = "--out=%s")
    args.add("--")
    args.add_all(ctx.attr.args)

    ctx.actions.run(
        executable = ctx.executable._process_wrapper,
        mnemonic = "VerilatorASTgen",
        arguments = [args],
        inputs = ctx.files.srcs,
        outputs = ctx.outputs.outs,
        tools = [ctx.file.astgen],
    )

    return [DefaultInfo(
        files = depset(ctx.outputs.outs),
    )]

verilator_astgen = rule(
    doc = "Run Verilator's `astgen` tool and collect the requested outputs.",
    implementation = _verilator_astgen_impl,
    attrs = {
        "args": attr.string_list(
            doc = "The command line arugments for `astgen`.",
        ),
        "astgen": attr.label(
            doc = "The path to the `astgen` tool.",
            allow_single_file = True,
            mandatory = True,
        ),
        "outs": attr.output_list(
            doc = "The output sources generated by `astgen`.",
            allow_empty = False,
            mandatory = True,
        ),
        "srcs": attr.label_list(
            doc = "Input sources for `astgen`.",
            allow_files = True,
        ),
        "_process_wrapper": attr.label(
            cfg = "exec",
            executable = True,
            default = Label("//private:verilator_astgen"),
        ),
    },
)

def _correct_bison_env_for_action(env, bison):
    """Modify the Bison environment variables to work in an action that doesn't a have built bison runfiles directory.

    The `bison_toolchain.bison_env` parameter assumes that Bison will provided via an executable attribute
    and thus have built runfiles available to it. This is not the case for this action and any other actions
    trying to use bison as a tool via the toolchain. This function transforms existing environment variables
    to support running Bison as desired.

    Args:
        env (dict): The existing bison environment variables
        bison (File): The Bison executable

    Returns:
        Dict: Environment variables required for running Bison.
    """
    bison_env = dict(env)

    # Convert the environment variables to non-runfiles forms
    bison_runfiles_dir = "{}.runfiles/{}".format(
        bison.path,
        bison.owner.workspace_name,
    )

    bison_env["BISON_PKGDATADIR"] = bison_env["BISON_PKGDATADIR"].replace(
        bison_runfiles_dir,
        "external/{}".format(bison.owner.workspace_name),
    )
    bison_env["M4"] = bison_env["M4"].replace(
        bison_runfiles_dir,
        "{}/external/{}".format(bison.root.path, bison.owner.workspace_name),
    )

    return bison_env

def _verilator_bisonpre_impl(ctx):
    bison_toolchain = ctx.toolchains["@rules_bison//bison:toolchain_type"].bison_toolchain

    args = ctx.actions.args()
    args.add(ctx.file.bisonpre)
    args.add("--yacc", bison_toolchain.bison_tool.executable)
    args.add("-d")
    args.add("-v")
    args.add("-o", ctx.outputs.out_src)
    args.add(ctx.file.yacc_src)

    outputs = [
        ctx.outputs.out_src,
        ctx.outputs.out_hdr,
    ]

    tools = depset([ctx.file.bisonpre], transitive = [bison_toolchain.all_files])

    bison_env = _correct_bison_env_for_action(
        env = bison_toolchain.bison_env,
        bison = bison_toolchain.bison_tool.executable,
    )

    ctx.actions.run(
        outputs = outputs,
        inputs = [ctx.file.yacc_src],
        tools = tools,
        executable = ctx.executable._process_wrapper,
        arguments = [args],
        mnemonic = "VerilatorBisonPre",
        use_default_shell_env = False,
        env = bison_env,
    )

    return DefaultInfo(
        files = depset(outputs),
        runfiles = ctx.runfiles(files = outputs),
    )

verilator_bisonpre = rule(
    doc = "Run Verilator's `bisonpre` tool and collect the requested outputs.",
    implementation = _verilator_bisonpre_impl,
    attrs = {
        "bisonpre": attr.label(
            doc = "The path to the `bisonpre` tool.",
            allow_single_file = True,
            mandatory = True,
            cfg = "exec",
        ),
        "out_hdr": attr.output(
            mandatory = True,
            doc = "Output files generated by the action.",
        ),
        "out_src": attr.output(
            mandatory = True,
            doc = "Output files generated by the action.",
        ),
        "yacc_src": attr.label(
            doc = "The yacc file to run on.",
            allow_single_file = True,
            mandatory = True,
        ),
        "_process_wrapper": attr.label(
            executable = True,
            cfg = "exec",
            default = Label("//private:verilator_bisonpre"),
        ),
    },
    toolchains = [
        "@rules_bison//bison:toolchain_type",
        "@rules_m4//m4:toolchain_type",
    ],
)

def _find_flex_src(ctx):
    cc_srcs = ctx.attr.src[OutputGroupInfo].cc_srcs.to_list()
    if len(cc_srcs) != 1:
        fail("Unexpected number of cc sources generated in `{}`: {}".format(
            ctx.attr.src.label,
            cc_srcs,
        ))

    return cc_srcs[0]

def _verilator_flexfix_impl(ctx):
    src = _find_flex_src(ctx)

    args = ctx.actions.args()
    args.add("--flexfix", ctx.file.flexfix)
    args.add("--src", src)
    args.add("--output", ctx.outputs.out)
    args.add("--")
    args.add_all(ctx.attr.args)

    ctx.actions.run(
        executable = ctx.executable._process_wrapper,
        mnemonic = "VerilatorFlexFix",
        outputs = [ctx.outputs.out],
        inputs = [src],
        tools = [ctx.file.flexfix],
        arguments = [args],
    )

    return [DefaultInfo(
        files = depset([ctx.outputs.out]),
    )]

verilator_flexfix = rule(
    doc = "Run Verilator's `flexfix` tool and collect the requested outputs.",
    implementation = _verilator_flexfix_impl,
    attrs = {
        "args": attr.string_list(
            doc = "The command line arugments for `flexfix`.",
        ),
        "flexfix": attr.label(
            doc = "The path to the `flexfix` tool.",
            cfg = "exec",
            allow_single_file = True,
            mandatory = True,
        ),
        "out": attr.output(
            doc = "The output source generated by `flexfix`.",
            mandatory = True,
        ),
        "src": attr.label(
            doc = "The source file to pass to `flexfix`.",
            mandatory = True,
            allow_files = True,
        ),
        "_process_wrapper": attr.label(
            cfg = "exec",
            executable = True,
            default = Label("//private:verilator_flexfix"),
        ),
    },
)

def _verilator_version_impl(ctx):
    output = ctx.actions.declare_file(ctx.label.name + ".txt")

    args = ctx.actions.args()
    args.add("--output", output)
    args.add("--changelog", ctx.file.changelog)

    ctx.actions.run(
        executable = ctx.executable._parser,
        mnemonic = "VerilatorVersion",
        outputs = [output],
        inputs = [ctx.file.changelog],
        arguments = [args],
    )

    return [DefaultInfo(
        files = depset([output]),
    )]

verilator_version = rule(
    doc = "A rule for parsing the current verilator version from the change log.",
    implementation = _verilator_version_impl,
    attrs = {
        "changelog": attr.label(
            doc = "The Verilator change log.",
            allow_single_file = True,
            mandatory = True,
        ),
        "_parser": attr.label(
            executable = True,
            cfg = "exec",
            default = Label("//private:verilator_version"),
        ),
    },
)

def _verilator_build_template_impl(ctx):
    output = ctx.outputs.out

    args = ctx.actions.args()
    args.add("--output", output)
    args.add("--version", ctx.file.version)
    args.add("--substitutions", json.encode(ctx.attr.substitutions))
    args.add("--template", ctx.file.template)

    ctx.actions.run(
        executable = ctx.executable._generator,
        mnemonic = "VerilatorBuildTemplate",
        outputs = [output],
        inputs = [ctx.file.version, ctx.file.template],
        arguments = [args],
    )

    return [DefaultInfo(
        files = depset([output]),
    )]

verilator_build_template = rule(
    doc = "A rule for expanding verilator template files required for compiling.",
    implementation = _verilator_build_template_impl,
    attrs = {
        "out": attr.output(
            doc = "The output file",
            mandatory = True,
        ),
        "substitutions": attr.string_dict(
            doc = "A mapping of substitutions to apply on the template file.",
            mandatory = True,
        ),
        "template": attr.label(
            doc = "The base template to apply substitutions to",
            mandatory = True,
            allow_single_file = True,
        ),
        "version": attr.label(
            doc = (
                "A file containing the current version of Verilator. In substitution " +
                "values, the `{VERILATOR_VERSION}` string will be replaced by the version " +
                "in this file."
            ),
            allow_single_file = True,
            mandatory = True,
        ),
        "_generator": attr.label(
            executable = True,
            cfg = "exec",
            default = Label("//private:verilator_build_template"),
        ),
    },
)
